﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using TracedReality.RayTracing;
using QiHe.Yaml.Grammar;

namespace TracedReality.Primitive
{
    /// <summary>
    /// A rectangular prism.
    /// </summary>
    public class Box : Shape
    {
        /// <summary>
        /// Corner of this Box.
        /// </summary>
        public Vector corner;
        
        /// <summary>
        /// Size of this Box.
        /// </summary>
        public Vector size;

        /// <summary>
        /// The FinitePlanes that make up the faces of this Box.
        /// </summary>
        public FinitePlane[] faces;

        /// <summary>
        /// Create a Box with the given coner, size, and Material.
        /// </summary>
        /// <param name="c">Corner.</param>
        /// <param name="s">Size.</param>
        /// <param name="m">Material.</param>
        public Box(Vector c, Vector s, ref Material m)
        {
            corner = c;
            size = s;

            // Size should be all positive so adjust the corner to make that happen
            if (size.x < 0) { corner.x += size.x; size.x *= -1; }
            if (size.y < 0) { corner.y += size.y; size.y *= -1; }
            if (size.z < 0) { corner.z += size.z; size.z *= -1; }

            material = m;
            bound = new BoundingBox(corner - Settings.EPSILON, corner + size + Settings.EPSILON);

            Vector farcorner = corner + size;
            faces = new FinitePlane[6];
            faces[0] = new FinitePlane(corner + new Vector(0, size.y, 0), new Vector(size.x, 0, 0), new Vector(0, -size.y, 0), ref material);
            faces[1] = new FinitePlane(corner, new Vector(size.x, 0, 0), new Vector(0, 0, size.z), ref material);
            faces[2] = new FinitePlane(corner + new Vector(0, 0, size.z), new Vector(0, size.y, 0), new Vector(0, 0, -size.z), ref material);
            faces[3] = new FinitePlane(farcorner, new Vector(-size.x, 0, 0), new Vector(0, -size.y, 0), ref material);
            faces[4] = new FinitePlane(farcorner - new Vector(0, 0, size.z), new Vector(-size.x, 0, 0), new Vector(0, 0, size.z), ref material);
            faces[5] = new FinitePlane(farcorner, new Vector(0, -size.y, 0), new Vector(0, 0, -size.z), ref material);
        }

        public override bool Equals(Shape s)
        {
            if (s is Box) { return Equals((Box)s); }
            return false;
        }

        public bool Equals(Box b)
        {
            return corner.Equals(b.corner) &&
                size.Equals(b.size) &&
                material.Equals(b.material);
        }

        public override bool Similar(Shape s)
        {
            if (!(s is Box)) { return false; }
            Box b = (Box)s;

            return size.Equals(b.size) &&
                material.Equals(b.material);
        }

        /// <summary>
        /// Test if the given Ray intersects this Box.
        /// </summary>
        /// <param name="ray">Ray to test for intersection.</param>
        /// <param name="hit">Hit to update with intersection info.</param>
        /// <returns>1 if the Ray intersects this Box from the outside,
        /// -1 if the Ray intersects this Box from the inside,
        /// 0 if no intersection occurs.</returns>
        public override int Intersect(Ray ray, Hit hit)
        {
            int result = 0;
            double[] hits = new double[6];
            for (int a = 0; a < 6; a++)
            {
                switch (faces[a].Intersect(ray, hit))
                {
                    case 0 :
                        break;
                    case 1 :
                        result = 1;
                        break;
                    case -1 :
                        result = -1;
                        break;
                }
            }

            return result;
        }

        public static Box Parse(MappingEntry entry)
        {
            if (entry == null) { throw new ArgumentNullException("entry"); }

            Mapping map = (Mapping)entry.Value;
            Material material = Material.Parse(map.Find("material"));
            MappingEntry found = map.Find("corner");
            if (found == null) { throw new ArgumentException("No corner entry"); }
            Vector corner = Vector.FromSequence((Sequence)found.Value);
            found = map.Find("size");
            if (found == null) { throw new ArgumentException("No size entry"); }
            Vector size = Vector.FromSequence((Sequence)found.Value);

            return new Box(corner, size, ref material);
        }

        /// <summary>
        /// Get a string representation of this Box.
        /// </summary>
        /// <returns>A string representing this Box.</returns>
        public override string ToString()
        {
            return "Box: { corner: " + corner + ", size: " + size + ", material: " + material + " }";
        }
    }
}
